package com.airport.schedule;

import com.airport.schedule.model.AdHocRule;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import com.airport.schedule.util.DateUtil;
import com.airport.schedule.util.FileUtil;
import com.airport.schedule.util.ListUtil;
import edu.stanford.nlp.util.IntervalTree;

import java.io.IOException;
import java.util.*;

public class ScheduleSolution {

    private final Map<FlightGate, List<Flight>> schedule;
    private final Map<FlightGate, IntervalTree<Integer, Flight>> scheduleTree;
    private final Map<Flight, FlightGate> flight2Gate;
    private final Problem problem;
    private final Set<Flight> assignedFlights;

    private int nearGateFlightNum;
    private int nearGatePeopleNum;
    private int adHocFlightNum;
    private int scheduledFlightNum;
    private int totalPeopleNum;

    public ScheduleSolution(Problem problem) {
        this.problem = problem;
        this.schedule = new HashMap<>(problem.getScheduleGates().size());
        this.scheduleTree = new HashMap<>(problem.getScheduleGates().size());
        this.assignedFlights = new HashSet<>(problem.getScheduleFlights().size());
        for(FlightGate gate : problem.getScheduleGates()) {
            schedule.put(gate, new ArrayList<>());
            scheduleTree.put(gate, new IntervalTree<>());
        }
        this.flight2Gate = new HashMap<>(problem.getScheduleFlights().size());
    }

    public ScheduleSolution(Problem problem, ScheduleSolution solution) {
        this(problem);
        long time = DateUtil.getTime(problem.getDate()) + 600;
        solution.schedule.forEach((gate, flights)->
                ListUtil.filter(flights, f->f.getDepartureTime() > time)
                        .forEach(f->assign(f, gate)));
    }

    public boolean isAssigned(Flight flight) {
        return assignedFlights.contains(flight);
    }

    // 将航班分配到该机位
    public void assign(Flight flight, FlightGate gate) {
        this.schedule.get(gate).add(flight);
        this.scheduleTree.get(gate).add(flight);
        this.flight2Gate.put(flight, gate);
        if(gate.isNearGate()) {
            nearGateFlightNum++;
            nearGatePeopleNum += flight.getNumPeople();
        }
        if(gate.isAdHocGate()) {
            adHocFlightNum++;
        }
        assignedFlights.add(flight);
        scheduledFlightNum++;
        totalPeopleNum += flight.getNumPeople();
    }

    // 机位当前是否满了
    public boolean isFull(FlightGate gate, Flight flight) {
        return scheduleTree.get(gate).overlaps(flight);
    }

    public Map<FlightGate, List<Flight>> getSchedule() {
        return schedule;
    }

    public Map<FlightGate, IntervalTree<Integer, Flight>> getScheduleTree() {
        return scheduleTree;
    }

    // 将航班从机位上移除
    public void remove(Flight flight, FlightGate gate) {
        boolean rmSuc = this.schedule.get(gate).remove(flight);
        if(rmSuc) {
            this.scheduleTree.get(gate).remove(flight);
            this.flight2Gate.remove(flight);
            if (gate.isNearGate()) {
                nearGateFlightNum--;
                nearGatePeopleNum -= flight.getNumPeople();
            }
            if (gate.isAdHocGate()) {
                adHocFlightNum--;
            }
            scheduledFlightNum--;
            assignedFlights.remove(flight);
            totalPeopleNum -= flight.getNumPeople();
        }
    }

    // 合并其他方案
    public void merge(ScheduleSolution solution) {
        this.schedule.putAll(solution.schedule);
        this.scheduledFlightNum += solution.scheduledFlightNum;
        this.adHocFlightNum += solution.adHocFlightNum;
        this.nearGateFlightNum += solution.nearGateFlightNum;
        this.nearGatePeopleNum += solution.nearGatePeopleNum;
        this.totalPeopleNum += solution.totalPeopleNum;
        this.problem.merge(solution.problem);
        this.flight2Gate.putAll(solution.flight2Gate);
        this.scheduleTree.putAll(solution.scheduleTree);
        this.assignedFlights.addAll(solution.assignedFlights);
    }

    // 合并其他天的方案
    public ScheduleSolution add(ScheduleSolution solution) {
        ScheduleSolution merged = new ScheduleSolution(problem);
        schedule.keySet().forEach(gate->{
            Set<Flight> flights = new HashSet<>(schedule.get(gate));
            flights.addAll(solution.schedule.get(gate));
            flights.forEach(f->merged.assign(f, gate));
        });
        return merged;
    }

    // 获取当前可停靠机位
    public Set<FlightGate> getCurrentAvailableGates(Flight flight, boolean considerAdHoc) {
        Set<FlightGate> unoccupiedGates = new HashSet<>();
        unoccupiedGates.addAll(getUnoccupiedNormalFlightGates(flight, considerAdHoc));
        if(considerAdHoc) {
            unoccupiedGates.addAll(getAvailableAdHocFlightGates(flight));
        }
        return unoccupiedGates;
    }

    // 获取当前未被占用的可停靠非临时措施机位
    public List<FlightGate> getUnoccupiedNormalFlightGates(Flight flight, boolean considerAdHoc) {
        List<FlightGate> unoccupiedGates = new ArrayList<>();
        for(FlightGate gate : problem.getNormalGates()) {
            if(gate.acceptFlight(flight)) { // 非临时并且可以停靠的机位
                if(!scheduleTree.get(gate).overlaps(flight)) { // 没有冲突
                    unoccupiedGates.add(gate);
                }
            }
        }
        if(considerAdHoc) {
            Set<FlightGate> cannotUse = new HashSet<>();
            for(FlightGate gate : unoccupiedGates) {
                if(!willNotBeAffectedByOtherFlights(flight, gate)) {
                    cannotUse.add(gate);
                }
            }
            unoccupiedGates.removeAll(cannotUse);
        }
        return unoccupiedGates;
    }

    // 获取当前可以停靠的临时措施机位
    public List<FlightGate> getAvailableAdHocFlightGates(Flight flight) {
        List<FlightGate> availableAdHocGates = new ArrayList<>();
        for(FlightGate gate : problem.getAdHocGates()) {
            if(gate.acceptFlight(flight)) {
                if(!scheduleTree.get(gate).overlaps(flight)
                        && !willAffectOtherFlights(flight, gate)) {
                    availableAdHocGates.add(gate);
                }
            }
        }
        return availableAdHocGates;
    }

    // 获取该机位分配的航班
    public List<Flight> getAssignedFlightsByFlightGate(FlightGate gate) {
        return schedule.get(gate);
    }

    // 获取被安排到机位的航班的总人数
    public int getTotalPeopleNum() {
        return totalPeopleNum;
    }

    // 获取被安排到机位的航班数
    public int getScheduledFlightNum() {
        return scheduledFlightNum;
    }

    // 获取实际参与调度航班的数量
    public int getInvolvedFlightNum() {
        return problem.getScheduleFlights().size();
    }

    // 近机位航班数
    public int getNearGateFlightNum() {
        return nearGateFlightNum;
    }

    // 近机位旅客数
    public int getNearGatePeopleNum() {
        return nearGatePeopleNum;
    }

    // 采取临时措施的航班数量
    public int getAdHocFlightNum() {
        return adHocFlightNum;
    }

    // 滑道冲突航班数
    public int getRunwayConflictNum() {
        Map<String, List<Flight>> runwayFlights = new HashMap<>();
        for(Map.Entry<FlightGate, List<Flight>> entry : schedule.entrySet()) {
            FlightGate gate = entry.getKey();
            if(!gate.hasNoRunwayConflict()) {
                List<Flight> flights = entry.getValue();
                String runwayIn = gate.getRunwayIn();
                String runwayOut = gate.getRunwayOut();
                List<Flight> flightOnRunwayIn = runwayFlights.getOrDefault(runwayIn, new ArrayList<>());
                flightOnRunwayIn.addAll(flights);
                runwayFlights.put(runwayIn, flightOnRunwayIn);
                if(!runwayIn.equals(runwayOut)) {
                    List<Flight> flightOnRunwayOut = runwayFlights.getOrDefault(runwayOut, new ArrayList<>());
                    flightOnRunwayOut.addAll(flights);
                    runwayFlights.put(runwayOut, flightOnRunwayOut);
                }
            }
        }
        return countRunwayConflictNum(runwayFlights);
    }

    // 机位冲突数
    public int getFlightGateConflictNum() {
        Set<Flight> gateConflicts = new HashSet<>();
        for(Map.Entry<FlightGate,List<Flight>> entry : schedule.entrySet()) {
            for(Flight flight : entry.getValue()) {
                List<Flight> conflicts = scheduleTree.get(entry.getKey()).getOverlapping(flight);
                conflicts.remove(flight);
                gateConflicts.addAll(conflicts);
            }
        }
        return gateConflicts.size() + countAdHocConflicts();
    }

    // 导出方案
    public void dumpSchedule(String filePath) throws IOException {
        List<String[]> scheduleList = new ArrayList<>();
        schedule.forEach((gate, flights)->
                flights.forEach((flight) -> {
                    String[] detail = new String[4];
                    detail[0] = flight.getFlightId();
                    detail[1] = DateUtil.format(flight.getArrivalTime());
                    detail[2] = DateUtil.format(flight.getDepartureTime());
                    detail[3] = gate.getFlightGateId();
                    scheduleList.add(detail);
                })
        );
        FileUtil.saveList2CSVFile(scheduleList, filePath);
    }


    // 获得问题
    public Problem getProblem() {
        return problem;
    }

    // 获取航班对应的机位
    public FlightGate getFlightGateByFlight(Flight flight) {
        for(Map.Entry<FlightGate, List<Flight>> entry : schedule.entrySet()) {
            if(entry.getValue().contains(flight)) {
                return entry.getKey();
            }
        }
        return null;
    }

    // ====================================private method====================================================

    private int countRunwayConflictNum(Map<String, List<Flight>> runwayFlights) {
        Set<Flight> conflicts = new HashSet<>();
        for(List<Flight> flights : runwayFlights.values()) {
            IntervalTree<Integer,Flight> tree = new IntervalTree<>();
            tree.addAll(flights);
            for(int i = 0; i < flights.size(); i++) {
                Flight f1 = flights.get(i);
                for(Flight f2 : tree.getOverlapping(f1)) {
                    if(ConflictHelper.hasRunwayConflict(f1, flight2Gate.get(f1), f2, flight2Gate.get(f2))) {
                        conflicts.add(f1);
                        conflicts.add(f2);
                    }
                }
            }
        }
        return conflicts.size();
    }

    private int countAdHocConflicts() {
        Set<Flight> conflicts = new HashSet<>();
        Set<String> alreadyComparedGates = new HashSet<>();

        for(Map.Entry<FlightGate, List<Flight>> entry : schedule.entrySet()) {
            FlightGate adHocGate = entry.getKey(); // 临时机位
            List<Flight> adHocFlights = entry.getValue(); // 临时机位上的航班
            if(adHocGate.isAdHocGate() && !adHocFlights.isEmpty()) { // 有航班采取了临时措施
                AdHocRule rule = adHocGate.getRule();

                // 同机位上的航班
                Set<String> homogenyIds = rule.getHomogenyFlightGates();
                homogenyIds.removeAll(filterHasCompared(alreadyComparedGates, "HOMOGENY",
                        adHocGate.getFlightGateId(), homogenyIds));
                Set<Flight> homogenyFlights = getFlightsByGateIds(homogenyIds);
                IntervalTree<Integer, Flight> homoTree = new IntervalTree<>();
                homoTree.addAll(homogenyFlights);
                for(Flight f1 : adHocFlights) {
                    List<Flight> overlaps = homoTree.getOverlapping(f1);
                    if(!overlaps.isEmpty()) {
                        conflicts.add(f1);
                        conflicts.addAll(overlaps);
                    }
                }

                // 被阻挡滑入机位的航班
                Set<Flight> obstructRunInFlights = getFlightsByGateIds(rule.getObstructRunInFlightGates());
                for(Flight f1 : adHocFlights) {
                    for(Flight f2 : obstructRunInFlights) {
                        if(!ConflictHelper.canEnterFlightGate(f2, f1)) {
                            conflicts.add(f1);
                            conflicts.add(f2);
                        }
                    }
                }

                // 被阻挡滑入滑出机位的航班
                Set<String> obstructIds = rule.getObstructFlightGates();
                obstructIds.removeAll(filterHasCompared(alreadyComparedGates, "OBSTRUCT",
                        adHocGate.getFlightGateId(), obstructIds));
                Set<Flight> obstructFlights = getFlightsByGateIds(obstructIds);
                for(Flight f1 : adHocFlights) {
                    for(Flight f2 : obstructFlights) {
                        if (!ConflictHelper.canEnterOrExitFlightGate(f2, f1)) {
                            conflicts.add(f1);
                            conflicts.add(f2);
                        }
                    }
                }
            }
        }
        return conflicts.size();
    }

    private Collection<String> filterHasCompared(Set<String> cache, String type, String gateId, Set<String> gateIds) {
        Set<String> alreadyCompared = new HashSet<>();
        for(String compareId : gateIds) {
            String record1 = type+gateId+compareId;
            String record2 = type+compareId+gateId;
            if(cache.contains(record1)) {
                alreadyCompared.add(compareId);
            }else{
                cache.add(record1);
            }
            if(cache.contains(record2)) {
                alreadyCompared.add(compareId);
            }else{
                cache.add(record2);
            }
        }
        return alreadyCompared;
    }

    private Set<Flight> getFlightsByGateIds(Set<String> gateIds) {
        Set<Flight> flights = new HashSet<>();
        for(String gateId : gateIds) {
            FlightGate gate = problem.getFlightGateById(gateId);
            flights.addAll(schedule.get(gate));
        }
        return flights;
    }

    private Set<Flight> getOverlapFlightsByGateIds(Set<String> gateIds, Flight flight) {
        Set<Flight> flights = new HashSet<>();
        for(String gateId : gateIds) {
            FlightGate gate = problem.getFlightGateById(gateId);
            flights.addAll(scheduleTree.get(gate).getOverlapping(flight));
        }
        return flights;
    }

    // 停靠在此处不会被影响
    private boolean willNotBeAffectedByOtherFlights(Flight flight, FlightGate flightGate) {
        Set<FlightGate> adHocGates = problem.getAttachedAdhocGates(flightGate);
        if(adHocGates != null) {
            for(FlightGate adHocGate : adHocGates) {
                List<Flight> flights = this.scheduleTree.get(adHocGate).getOverlapping(flight);
                if(flights.isEmpty()) continue;
                AdHocRule rule = adHocGate.getRule();
                if(rule.getObstructFlightGates().contains(flightGate.getFlightGateId())) {
                    for(Flight f : flights) {
                        if(!ConflictHelper.canEnterOrExitFlightGate(flight, f))
                            return false;
                    }
                }
                if(rule.getHomogenyFlightGates().contains(flightGate.getFlightGateId())) {
                    for(Flight f : flights) {
                        if(ConflictHelper.hasFlightGateConflict(f, flight))
                            return false;
                    }
                }
                if(rule.getObstructRunInFlightGates().contains(flightGate.getFlightGateId())) {
                    for(Flight f : flights) {
                        if(!ConflictHelper.canEnterFlightGate(flight, f))
                            return false;
                    }
                }
            }
        }
        return true;
    }

    // 当前航班如若停靠在adHocGate 是否会影响其他航班
    private boolean willAffectOtherFlights(Flight flight, FlightGate adHocGate) {
        AdHocRule rule = adHocGate.getRule();
        Set<Flight> obsRunInFlights = getOverlapFlightsByGateIds(rule.getObstructRunInFlightGates(), flight);
        Set<Flight> obsFlights = getOverlapFlightsByGateIds(rule.getObstructFlightGates(), flight);
        Set<Flight> homoFlights = getOverlapFlightsByGateIds(rule.getHomogenyFlightGates(), flight);
        for(Flight f : obsFlights) {
            if(!ConflictHelper.canEnterOrExitFlightGate(f, flight))
                return true;
        }
        for(Flight f : obsRunInFlights) {
            if(!ConflictHelper.canEnterFlightGate(f, flight))
                return true;
        }
        for(Flight f : homoFlights) {
            if(ConflictHelper.hasFlightGateConflict(f, flight))
                return true;
        }
        return !willNotBeAffectedByOtherFlights(flight, adHocGate);
    }

}
